package assg.funnycells.server;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.text.*;
import java.util.Hashtable;

import assg.funnycells.util.*;

public class ActivityDisplay implements ActionListener, WindowListener {
    public final static Color bordercolor = Color.decode("#dcdcdc");
    public final static Color background = Color.decode("#ffffff");
    public final static Color[] cellBackground = new Color[]{Color.decode("#ffff00"), 
							     Color.decode("#ff0000"),
							     Color.decode("#00ff00")};

    public boolean TIMER = true;

    Configuration config;
    Logger logger;
    JLabel[][] cells;
    int[][][] count;
    int[][][] chemCount;
    int height, width, windowheight, windowwidth, dispheight, dispwidth, counterheight, counterwidth, cellWidth, cellHeight;
    Hashtable<String, Integer> colors;
    char[] invColors;
    int colorCount;
    int maxChemCount = 3;

    ServerState serverState;

    JLabel numSusceptible, numInfected, numAntibody, numInfections, numImmunisations, infectedEnergyUse, antibodyEnergyUse, infectedCoordination,
	antibodyCoordination;
    TimerLabel timerLabel;
    JFrame displayFrame;
    String infectedTeamName, antibodyTeamName;

    public ActivityDisplay(String title, boolean displayOn, long termination, String infectedTeamName, String antibodyTeamName) {
	displayFrame = null;

	config = Coordinator.getConfig();
	logger = Logger.getDefaultLogger();
	
	colors = new Hashtable<String, Integer>();
	invColors = new char[maxChemCount];
	colorCount = 0;

	height = StringOperations.parseInt(config.getVal(Configuration.MAP, Configuration.HEIGHT));
	width = StringOperations.parseInt(config.getVal(Configuration.MAP, Configuration.WIDTH));

	count = new int[height][width][3];
	chemCount = new int[height][width][maxChemCount];

	this.infectedTeamName = infectedTeamName;
	this.antibodyTeamName = antibodyTeamName;

	if(termination <= 1)
	    TIMER = false;

	if(displayOn) {
	    displayFrame = new JFrame(title.equals("") ? "Funnycells network map" : title);
	    
	    displayFrame.setBounds(0, 0, windowwidth = StringOperations.parseInt(config.getVal(Configuration.DISPLAY, Configuration.DISPWIDTH)),
				   windowheight = StringOperations.parseInt(config.getVal(Configuration.DISPLAY, Configuration.DISPHEIGHT)));
	    dispheight = (int)(windowheight * 0.75);
	    dispwidth = windowwidth;
	    counterheight = windowheight - dispheight;
	    counterwidth = windowwidth;
	    displayFrame.setResizable(false);
	    displayFrame.setBackground(background);
	    
	    displayFrame.setLayout(new GridBagLayout());
	    GridBagConstraints c = new GridBagConstraints();
	    
	    JPanel activityPanel = new JPanel();
	    activityPanel.setPreferredSize(new Dimension(dispwidth, dispheight));
	    activityPanel.setMinimumSize(new Dimension(dispwidth, dispheight));
	    c.gridx = 0; c.gridy = 0; c.gridwidth = 1; c.gridheight = 1;
	    c.fill = GridBagConstraints.BOTH;
	    displayFrame.add(activityPanel, c);
	    
	    JPanel counterPanel = new JPanel();
	    counterPanel.setPreferredSize(new Dimension(counterwidth, counterheight));
	    counterPanel.setMinimumSize(new Dimension(counterwidth, counterheight));
	    c = new GridBagConstraints();
	    c.gridx = 0; c.gridy = 1; c.gridwidth = 1; c.gridheight = 1;
	    c.fill = GridBagConstraints.BOTH;
	    displayFrame.add(counterPanel, c);
	    
	    buildMap(activityPanel);
	    buildCounters(counterPanel);
	    
	    displayFrame.setVisible(true);
	    displayFrame.addWindowListener(this);
	} else {
	    displayFrame = null;
	}
    }

    public void setServerState(ServerState serverState) {
	this.serverState = serverState;
    }

    protected void buildCounters(JPanel panel) {
	panel.setLayout(new GridBagLayout());
	Dimension cellDim = new Dimension((int)(counterwidth / 6), (int)(counterheight / 5));

	addLabel("Susceptible", numSusceptible = new JLabel(), panel, cellDim, 0, 0, Color.yellow);
	addLabel(infectedTeamName, numInfected = new JLabel(), panel, cellDim, 0, 1, Color.red);
	addLabel(antibodyTeamName, numAntibody = new JLabel(), panel, cellDim, 0, 2, Color.green);
	addLabel("Infections", numInfections = new JLabel(), panel, cellDim, 1, 0, Color.white);
	addLabel("Immunisations", numImmunisations = new JLabel(), panel, cellDim, 2, 0, Color.white);
	addLabel("Infected energy", infectedEnergyUse = new JLabel(), panel, cellDim, 1, 1, Color.white);
	addLabel("Antibody energy", antibodyEnergyUse = new JLabel(), panel, cellDim, 2, 1, Color.white);

	if(TIMER) {
	    addLabel("Timer", timerLabel = new TimerLabel(), panel, cellDim, 1, 2, Color.white);
	    addLabel("", new JLabel(), panel, cellDim, 2, 2, Color.white);
	} else {
	    addLabel("Infected coord", infectedCoordination = new JLabel(), panel, cellDim, 1, 2, Color.white);
	    addLabel("Antibody coord", antibodyCoordination = new JLabel(), panel, cellDim, 2, 2, Color.white);

	    GridBagConstraints c = new GridBagConstraints();
	    c.gridx = 0; c.gridy = 3; c.gridwidth = 6; c.gridheight = 1;
	    c.fill = GridBagConstraints.BOTH;
	    JButton updateButton = new JButton("Update coordination metric");
	    updateButton.setPreferredSize(cellDim = new Dimension(counterwidth, counterheight / 5));
	    updateButton.setMinimumSize(cellDim);
	    updateButton.setActionCommand("update");
	    updateButton.addActionListener(this);
	    panel.add(updateButton, c);
	}
    }

    protected void buildMap(JPanel panel) {
	cells = new JLabel[height][width];

	panel.setLayout(new GridBagLayout());
	GridBagConstraints c;
	Dimension cellDim = new Dimension(cellWidth = (int)((double)dispwidth / (width + 1)) - 1, 
					  cellHeight = (int)((double)dispheight / (height + 1)) - 3);
	for(int i = 0; i < height; i++) {
	    for(int j = 0; j < width; j++) {
		c = new GridBagConstraints();
		c.gridx = (j + 1);
		c.gridy = (i + 1);
		c.gridwidth = 1;
		c.gridheight = 1;
		c.fill = GridBagConstraints.BOTH;
		cells[i][j] = new JLabel();
		cells[i][j].setPreferredSize(cellDim);
		cells[i][j].setMinimumSize(cellDim);
		cells[i][j].setText("");
		cells[i][j].setBorder(BorderFactory.createLineBorder(bordercolor, 1));
		cells[i][j].setHorizontalAlignment(SwingConstants.CENTER);
		cells[i][j].setVerticalAlignment(SwingConstants.CENTER);
		cells[i][j].setBackground(background);
		cells[i][j].setOpaque(true);
		panel.add(cells[i][j], c);

		if(i == 0) {
		    c = new GridBagConstraints();
		    c.gridx = (j + 1);
		    c.gridy = 0;
		    c.gridwidth = 1;
		    c.gridheight = 1;
		    c.fill = GridBagConstraints.BOTH;
		    JLabel fillerLabel = new JLabel();
		    fillerLabel.setPreferredSize(cellDim);
		    fillerLabel.setMinimumSize(cellDim);
		    fillerLabel.setText(Integer.toString(j));
		    fillerLabel.setBorder(BorderFactory.createLineBorder(bordercolor, 1));
		    fillerLabel.setHorizontalAlignment(SwingConstants.CENTER);
		    fillerLabel.setVerticalAlignment(SwingConstants.CENTER);
		    panel.add(fillerLabel, c);
		}
	    }

	    c = new GridBagConstraints();
	    c.gridx = 0;
	    c.gridy = (i + 1);
	    c.gridwidth = 1;
	    c.gridheight = 1;
	    c.fill = GridBagConstraints.BOTH;
	    JLabel fillerLabel = new JLabel();
	    fillerLabel.setPreferredSize(cellDim);
	    fillerLabel.setMinimumSize(cellDim);
	    fillerLabel.setText(Integer.toString(i));
	    fillerLabel.setBorder(BorderFactory.createLineBorder(bordercolor, 1));
	    fillerLabel.setHorizontalAlignment(SwingConstants.CENTER);
	    fillerLabel.setVerticalAlignment(SwingConstants.CENTER);
	    panel.add(fillerLabel, c);
	}
    }

    public void startTimer(int timeRemaining) {
	if(displayFrame != null && TIMER)
	    timerLabel.startTimer(timeRemaining);
    }

    public void add(int x, int y, int type, String cellId) {
	count[y][x][type]++;
	update(x, y, cellId);
    }

    public void remove(int x, int y, int type) {
	count[y][x][type]--;
	update(x, y, "");
    }

    private void update(int x, int y, String cellId) {
	if(displayFrame != null) {
	    if(count[y][x][0] + count[y][x][1] + count[y][x][2] == 0) {
		cells[y][x].setBackground(background);
		cells[y][x].setText("");
		updateChem(x, y, -1);
	    } else {
		cells[y][x].setBackground(mix(x, y));
		cells[y][x].setText(Integer.toString(count[y][x][0] + count[y][x][1] + count[y][x][2]));
	    }
	}
    }

    public void addChem(int x, int y, String color) {
	if(color == null || color.equals("null") || color.equals("")) return;
	if(colors.get(color) == null) {
	    if(colorCount < maxChemCount) {
		colors.put(color, new Integer(colorCount));
		invColors[colorCount] = color.charAt(0);
		colorCount++;
	    } else {
		return;
	    }
	}
	int index = colors.get(color).intValue();
	chemCount[y][x][index]++;
	updateChem(x, y, index);
    }

    public void removeChem(int x, int y, String color) {
	if(color == null || color.equals("null") || color.equals("")) return;
	if(colors.get(color) != null) {
	    int index = colors.get(color).intValue();
	    chemCount[y][x][index]--;
	    updateChem(x, y, index);
	}
    }

    char[] colorCode = new char[maxChemCount];
    private void updateChem(int x, int y, int index) {
	if(displayFrame != null) {
	    if(index == -1) {
		int j = 0;
		for(int i = 0; i < maxChemCount; i++)
		    if(chemCount[y][x][i] > 0)
			colorCode[j++] = invColors[i];
		if(j > 0)
		    cells[y][x].setText(new String(colorCode, 0, j));
	    } else {
		if(chemCount[y][x][index] > 0 && cells[y][x].getText().indexOf(invColors[index]) > -1 ||
		   chemCount[y][x][index] == 0 && cells[y][x].getText().indexOf(invColors[index]) == 0)
		    return;
		else
		    updateChem(x, y, -1);
	    }
	}

	//	if(index == -1) {
	//	    for(index = 0; index < maxChemCount; index++)
	//		updateChem(x, y, index);
	//	    return;
	//	}
	//	System.out.println("***** updating chemical at (" + x + "," + y + ") = " + chemCount[y][x][index] + ":" + invColors[index] + ":" + index);
	//	Graphics g = cells[y][x].getGraphics();
	//	if(chemCount[y][x][index] > 0)
	//	    g.setColor(invColors[index]);
	//	else
	//	    g.setColor(cells[y][x].getBackground());
	//	g.fillOval(cellWidth / 3 * (index % 3) + 2, cellHeight / 2 * (index % 2) + 2,
	//		   cellWidth / 3 - 4, cellHeight / 2 - 4);
    }
    
    private Color mix(int x, int y) {
	return new Color((count[y][x][0] * cellBackground[0].getRGB() + 
			  count[y][x][1] * cellBackground[1].getRGB() + 
			  count[y][x][2] * cellBackground[2].getRGB()) /
			 (count[y][x][0] + count[y][x][1] + count[y][x][2]));
    }

    private void addLabel(String label, JLabel counterLabel, JPanel panel, Dimension cellDim, int x, int y, Color color) {
	GridBagConstraints c = new GridBagConstraints();
	JLabel textLabel = new JLabel();
	textLabel.setText(label);
	textLabel.setPreferredSize(cellDim);
	textLabel.setMinimumSize(cellDim);
	textLabel.setBackground(color);
	textLabel.setOpaque(true);
	c.gridx = 2 * x; c.gridy = y;
	c.gridwidth = 1; c.gridheight = 1;
	c.fill = GridBagConstraints.BOTH;
	panel.add(textLabel, c);

	c = new GridBagConstraints();
	counterLabel.setPreferredSize(cellDim);
	counterLabel.setMinimumSize(cellDim);
	counterLabel.setBackground(color);
	counterLabel.setOpaque(true);
	c.gridx = 2 * x + 1; c.gridy = y;
	c.gridwidth = 1; c.gridheight = 1;
	c.fill = GridBagConstraints.BOTH;
	panel.add(counterLabel, c);
    }

    public void updateFrequentMetrics(int numSusceptible, int numInfected, int numAntibody,
				      int numInfections, int numImmunisations) {
	if(displayFrame != null) {
	    this.numSusceptible.setText(Integer.toString(numSusceptible));
	    this.numInfected.setText(Integer.toString(numInfected));
	    this.numAntibody.setText(Integer.toString(numAntibody));
	    this.numInfections.setText(Integer.toString(numInfections));
	    this.numImmunisations.setText(Integer.toString(numImmunisations));
	}
    }

    DecimalFormat twoD = new DecimalFormat("#.##");
    public void updateWindowedMetrics(double infectedEnergyUse, double antibodyEnergyUse) {
	if(displayFrame != null) {
	    this.infectedEnergyUse.setText(twoD.format(infectedEnergyUse));
	    this.antibodyEnergyUse.setText(twoD.format(antibodyEnergyUse));
	}
    }

    public void updateCoordinationMetrics(double infectedCoordination, double antibodyCoordination) {
	if(displayFrame != null && !TIMER) {
	    this.infectedCoordination.setText(twoD.format(infectedCoordination));
	    this.antibodyCoordination.setText(twoD.format(antibodyCoordination));
	}
    }

    public void actionPerformed(ActionEvent e) {
	if(e.getActionCommand().equals("update")) {
	    serverState.updateCoordinationMetrics();
	}
    }

    public void windowOpened(WindowEvent e) {
    }

    public void windowClosed(WindowEvent e) {
    }

    public void windowClosing(WindowEvent e) {
	try {
	    logger.info("Exiting cells");
	    serverState.exitSimul();
	} catch(Exception ex) {
	    logger.error("Error exiting cells", ex);
	}
	System.exit(0);
    }

    public void windowIconified(WindowEvent e) {
    }

    public void windowDeiconified(WindowEvent e) {
    }

    public void windowActivated(WindowEvent e) {
    }

    public void windowDeactivated(WindowEvent e) {
    }

    
    class TimerLabel extends JLabel implements ActionListener {
	int timeRemaining;
	Timer t;

	public TimerLabel() {
	    super("");
	    t = new Timer(1000, this);
	}

	public void startTimer(int timeRemaining) {
	    this.timeRemaining = timeRemaining;
	    setText(Integer.toString(timeRemaining));
	    t.start();
	}

	public void actionPerformed(ActionEvent ae) {
	    timeRemaining--;
	    setText(Integer.toString(timeRemaining));
	}
    }
}